package com.gitee.faster.webdav.kernel.protocol.dispatcher;

import com.gitee.faster.webdav.common.constant.HttpHeaderEnum;
import com.gitee.faster.webdav.common.constant.HttpStatusEnum;
import com.gitee.faster.webdav.common.constant.MethodEnum;
import com.gitee.faster.webdav.exception.NotFoundException;
import com.gitee.faster.webdav.kernel.model.*;
import com.gitee.faster.webdav.kernel.protocol.HttpExchange;
import com.gitee.faster.webdav.kernel.resource.Resource;
import org.smartboot.http.common.enums.HttpStatus;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * 负责检索资源的属性
 * 如果由 Request-URI 标识的资源没有任何内部成员，那么返回的就是这个资源上定义的属性
 * 如果由 Request-URI 标识的资源是个有内部成员 URLs 的集合，那么返回的就是它自身及其可能存在的成员资源上定义的属性
 * PROPFIND 请求必须包含一个值为 “0”、“1” 或 “infinity” 的 Depth header
 * 对于没有 Depth header 的请求，服务器应当视作 “Depth: infinity” 对待
 *
 * @author hongda.li
 */
public class MethodDispatcherPropFind implements MethodDispatcher {
    @Override
    public MethodEnum supported() {
        return MethodEnum.PROPFIND;
    }

    @Override
    public void dispatch(HttpExchange accessor) {
        // 查找属性
        PropFind propfind = accessor.parseXmlData(PropFind.class);

        // 访问深度
        int maxDepth = accessor.getConfiguration().getMaxDepth();
        int depth = accessor.getHeaderValue().getInt(HttpHeaderEnum.DEPTH.getHeader(), maxDepth);

        // 响应结果
        MultiStatus multistatus = new MultiStatus();

        // 文件列表
        List<Resource> resourceList = accessor.getRepository().walk(accessor.getResource(), depth);

        resourceList.forEach(resource -> {
            // 根据不同的查询类型进行查询
            switch (PropFind.FindType.fromProFind(propfind)) {
                case SEARCH_NAME:
                    this.searchName(multistatus);
                    break;
                case SEARCH_ALL:
                    Prop prop = Prop.allProp();
                    propfind.setProp(prop);
                    this.searchValue(multistatus, resource, propfind);
                    break;
                case SEARCH_VALUE:
                    this.searchValue(multistatus, resource, propfind);
                    break;
                default:
                    break;
            }

            Response response = multistatus.lastResponse();
            Objects.requireNonNull(response);
            response.addHref(resource);
        });

        // 写入响应数据
        accessor.setHttpStatus(HttpStatus.MULTI_STATUS);
        accessor.writeXmlResult(multistatus);
    }

    private void searchValue(MultiStatus multistatus, Resource resource, PropFind propfind) {
        // 文件或目录不存在
        if (!resource.exists()) {
            throw new NotFoundException("资源不存在 : " + resource.getName());
        }

        boolean isFile = resource.isFile();

        Prop expected = propfind.getProp();
        Prop prop = new Prop();

        // 归属者
        if (expected.getOwner() != null) {
            prop.setOwner(resource.getOwner());
        }

        // 资源名称
        if (expected.getDisplayName() != null) {
            prop.setDisplayName(DisplayName.byName(resource));
        }

        // 修改时间
        if (expected.getGetLastModified() != null) {
            prop.setGetLastModified(GetLastModified.byDate(resource.lastModified()));
        }

        // 文件大小
        if (expected.getGetContentLength() != null) {
            prop.setGetContentLength(GetContentLength.byLength(resource.length()));
        }

        // 文件类型
        if (expected.getGetContentType() != null && isFile) {
            prop.setGetContentType(GetContentType.byName(resource.getName()));
        }

        // 文件标识(文件大小-文件修改时间)
        if (expected.getGetEtag() != null && isFile) {
            prop.setGetEtag(GetEtag.byLengthAndDate(resource.length(), resource.lastModified()));
        }

        // 文件/目录标识
        prop.setResourceType(isFile ? ResourceType.ofFile() : ResourceType.ofDirectory());

        // 状态
        PropStat propstat = new PropStat();
        propstat.setProp(prop);
        propstat.setStatus(HttpStatusEnum.OK.getMessage());

        Response response = new Response();
        response.setPropStat(Collections.singletonList(propstat));

        multistatus.addResponse(response);
    }

    private void searchName(MultiStatus multistatus) {
        PropStat propstat = new PropStat();
        propstat.setProp(Prop.allProp());
        propstat.setStatus(HttpStatusEnum.OK.getMessage());

        Response response = new Response();
        response.setPropStat(Collections.singletonList(propstat));

        multistatus.addResponse(response);
    }
}
